#! /usr/bin/perl -w
#
# "Shell" for a restricted account, limiting the available commands
# This shell is particularly designed for Codendi restricted users:
# it denies access to cvs repositories of projects the user is not member of.
#
# Based on work by Roland Mas, debian-sf (Sourceforge for Debian)
# Inspired from the grap.c file in Sourceforge 2.5
#
# Modified by N. Guerin for the Codendi project.
# Copyright (c) Xerox Corporation, Codendi, 2005. All Rights Reserved
#
# 
#


use strict ;
use warnings ;
use vars qw/ @allowed_options @allowed_commands $errmsg @cmd / ;
use subs qw/ &reject / ;
no locale ;

@allowed_options = ('-c', '-e') ;
@allowed_commands = ('cvs', 'scp', 'sftp') ;

# Clean up our environment
delete @ENV{qw(IFS CDPATH ENV BASH_ENV PATH)};

if ($#ARGV != 1) {
    if ($#ARGV < 1) {
	$errmsg = "Not enough arguments." ;
    } else {
	$errmsg = "Too many arguments." ;
    }
    &reject ;
}

if (scalar (grep { $_ eq $ARGV[0] } @allowed_options) == 0) {
    $errmsg = "Option not allowed." ;
    &reject ;
}

@cmd = split (/ +/, $ARGV[1]) ;

if (scalar (grep { $_ eq $cmd[0] } @allowed_commands) == 0) {
    $errmsg = "Command not allowed." ;
    &reject ;
}

# Restricted Users
# Deny access to CVS repositories if the user is not a member
# of the project.
# For info on CVS client/server protocol, see:
# http://docs.freebsd.org/info/cvsclient/cvsclient.info.Requests.html
if ($ARGV[1] eq "cvs server") {
  # Read the inital request from the CVS client.
  # If the CVS root specified in this request does not correspond to
  # a project the user is member of, then deny the access.
  my $req='';
  while(<STDIN>) {
    $req .= $_;
    if (/^[a-z]/) {
      # starts with lower case: end of initial request
      if (! /gzip-file-contents/ ) { # ...except in this case
        last;
      }
    }
    if (m|^Root.*/([^/]+)$|) {
      # Get CVS root.
      # Ex: 'Root /cvsroot/gpig'
      my $cvsroot=$1;
      chomp $cvsroot;

      # Now get the list of groups (thus projects) the user
      # is a member of.
      my $grouplist=`/usr/bin/id -nG`;
      chomp $grouplist;

      my @groups=split / /,$grouplist;
      my $allowed=0;
      my $group='';
      foreach $group (@groups) {
        if ($group eq $cvsroot) {
          $allowed=1;
          last;
        }
      }
      if (!$allowed) {
        print "M Error: Access denied to this project\n";
        exit 1;
      }
    }
  }

  # cleanup old files in tmp (files older than one day with same owner)
  my $username=`/usr/bin/whoami`;
  chomp $username;
  `find /tmp -name "cvssh.*" -atime +1  -user $username -exec /bin/rm {} \\;`;

  # small trick: re-inject initial request with upcoming data from STDIN
  # ... there should be a simpler way, though.
  my $timestamp=`/bin/date +%s%N`;
  chomp $timestamp;
  open TMP,"> /tmp/cvssh.$$.$timestamp";
  print TMP $req;
  close TMP;
  system("cat /tmp/cvssh.$$.$timestamp - | cvs server");
  unlink "/tmp/cvssh.$$";
}

exec @cmd ;

sub reject {
    print "This is a restricted account.\n" . 
	"You cannot execute anything here.\n" . 
	# $errmsg . "\n" .
	"Goodbye.\n" ;
    exit 1 ;
}
